package org.liu.simpleTank;

import java.util.Iterator;
import java.util.ArrayList;
import java.util.EventObject;

public abstract class Element 
{
	// properties
	public int type;
	public int direction;
	public int col;
	public int row;
	public int w;
	public int h;
	{
		type = CONSTS.NONE;
		direction = CONSTS.UP;
		col = 0;
		row = 0;
		w = 1;
		h = 1;
	}
	
	public Element(int type, int w, int h) { this.type = type; this.w = w; this.h = h; }

	// cell related
	
	private Map map;
	private Cell topLeft;
	private Cell bottomRight;
	/**
	 * this method should be called by placeElement method of a map
	 * @param map
	 * @param x
	 * @param y
	 */
	public void addToMap(Map map, int row, int col) {
		// invalid position
		if(col - 1 + this.w >= CONSTS.MW || row - 1 + this.h >= CONSTS.MH)
			return;
		this.map = map;
		this.col = col;
		this.row = row;
		this.topLeft = map.getCell(row, col);
		this.bottomRight = map.getCell(row - 1 + h, col - 1 + w);
		// add reference to this element to the cells 
		Cell[] cells = allCell();
		for(int i = 0; i < cells.length; i++) {
			cells[i].add(this);
		}
	}
	
	// get an array of surrounding cells on one direction
	
	public Cell[] upRow() {
		Cell[] cells = new Cell[w];
		Cell tmp = topLeft.getUp();
		if(tmp == null) // no existence of cells up this element
			return null;
		for(int i = 0; i < w; i++) {
			cells[i] = tmp;
			tmp = tmp.getRight();
		}
		return cells;
	}
	// TODO fix naming issue
	public Cell[] righterRow() {
		Cell[] cells = new Cell[h];
		Cell tmp = bottomRight.getRight();
		if(tmp == null)
			return null;
		for(int i = 0; i < h; i++) {
			cells[i] = tmp;
			tmp = tmp.getUp();
		}
		return cells;
	}
	
	public Cell[] downRow() {
		Cell[] cells = new Cell[w];
		Cell tmp = bottomRight.getDown();
		if(tmp == null)
			return null;
		for(int i = 0; i < w; i++) {
			cells[i] = tmp;
			tmp = tmp.getLeft();
		}
		return cells;
	}
	// TODO fix naming issue
	public Cell[] lefterRow() {
		Cell[] cells = new Cell[h];
		Cell tmp = topLeft.getLeft();
		if(tmp == null)
			return null;
		for(int i = 0; i < h; i++) {
			cells[i] = tmp;
			tmp = tmp.getDown();
		}
		return cells;
	}
	
	public Cell[] cellOn(int dir) {
		switch(dir) {
		case CONSTS.UP: return upRow();
		case CONSTS.RIGHT: return righterRow();
		case CONSTS.DOWN: return downRow();
		case CONSTS.LEFT: return lefterRow();
		default: return null;
		}
	}
	
	public Cell[] allCell() {
		Cell[] cells = new Cell[w * h];
		Cell tmp = topLeft;
		Cell cell = null;
		for(int i = 0; i < h; i++) {
			cell = tmp;
			for(int j = 0; j < w; j++) {
				cells[i * w + j] = cell;
				cell = cell.getRight();
			}
			tmp = tmp.getDown();
		}
		
		return cells;
	}
	
	// remove a row|column of cells on one direction
	
	public boolean rmFirstRow() {
		if(h <= 1) // no contents after removal, invalid
			return false;
		Cell tmp = topLeft;
		for(int i = 0; i < w; i++) {
			tmp.remove(this);
			tmp = tmp.getRight();
		}
		h--;
		row++;
		topLeft = topLeft.getDown();
		return true;
	}
	
	public boolean rmLastRow() {
		if(h <= 1) // see rmFirstRow
			return false;
		Cell tmp = bottomRight;
		for(int i = 0; i < w; i++) {
			tmp.remove(this);
			tmp = tmp.getLeft();
		}
		h--;
		bottomRight = bottomRight.getUp();
		return true;
	}
	
	public boolean rmFirstCol() {
		if(w <= 1) // see rmFirstRow
			return false;
		Cell tmp = topLeft;
		for(int i = 0; i < h; i++) {
			tmp.remove(this);
			tmp = tmp.getDown();
		}
		w--;
		col++;
		topLeft = topLeft.getRight();
		return true;
	}
	
	public boolean rmLastCol() {
		if(w <= 1) // see rmFirstRow
			return false;
		Cell tmp = bottomRight;
		for(int i = 0; i < h; i++) {
			tmp.remove(this);
			tmp = tmp.getUp();
		}
		w--;
		bottomRight = bottomRight.getLeft();
		return true;
	}
	
	public void rm(int dir) {
		switch(dir) {
		case CONSTS.UP: rmFirstRow(); break;
		case CONSTS.RIGHT: rmLastCol(); break;
		case CONSTS.DOWN: rmLastRow(); break;
		case CONSTS.LEFT: rmFirstCol(); break;
		default: break;
		}
	}
	
	public void rmOpposite(int dir) {
		switch(dir) {
		case CONSTS.UP: rmLastRow(); break;
		case CONSTS.RIGHT: rmFirstCol(); break;
		case CONSTS.DOWN: rmFirstRow(); break;
		case CONSTS.LEFT: rmLastCol(); break;
		default: break;
		}
	}
	
	// expand this element on different direction
	
	/**
	 * expand this element by one row|column of cells on the direction indicated by <code>direction</code>
	 * @param direction
	 * @return
	 */
	public boolean expand(int direction) {
		switch(direction) {
		case CONSTS.UP: return expandUp();
		case CONSTS.RIGHT: return expandRight();
		case CONSTS.DOWN: return expandDown();
		case CONSTS.LEFT: return expandLeft();
		default: return false;
		}
	}
	
	/**
	 * expand down this element by one row of cells
	 * @return if this operation succeeded
	 */
	public boolean expandUp() {
		Cell[] cells = upRow();
		if(cells == null) // no existence of cells up this element
			return false;
		for(int i = 0; i < cells.length; i++) {
			cells[i].add(this);
		}
		row--;
		h++;
		topLeft = topLeft.getUp();
		return true;
	}
	
	/**
	 * expand this element by one column of cells to the right
	 * @return if this operation succeeded
	 */
	public boolean expandRight() {
		Cell[] cells = righterRow();
		if(cells == null)
			return false;
		for(int i = 0; i < cells.length; i++) {
			cells[i].add(this);
		}
		w++;
		bottomRight = bottomRight.getRight();
		return true;
	}
	
	/**
	 * expand down this element by one row of cells
	 * @return if this operation succeeded
	 */
	public boolean expandDown() {
		Cell[] cells = downRow();
		if(cells == null)
			return false;
		for(int i = 0; i < cells.length; i++) {
			cells[i].add(this);
		}
		h++;
		bottomRight = bottomRight.getDown();
		return true;
	}
	
	/**
	 * expand this element by one column of cells to the left
	 * @return if this operation succeeded
	 */
	public boolean expandLeft() {
		Cell[] cells = lefterRow();
		if(cells == null)
			return false;
		for(int i = 0; i < cells.length; i++) {
			cells[i].add(this);
		}
		col--;
		w++;
		topLeft = topLeft.getLeft();
		return true;
	}

	/**
	 * update this element on every tick
	 * @param ctx
	 */
	public abstract void update(UpdateContext ctx);
	
	// element destruction related
	public void destroy() {
		// remove references from cells
		Cell[] cells = allCell();
		for(int i = 0; i < cells.length; i++)
			cells[i].remove(this);
		
		// signal those who added this element to its list that this element is to be destroyed
		destroyed();
		
		// clear listeners
		listeners.clear();
	}
	
	private ArrayList<ElementListener> listeners;
	{
		listeners = new ArrayList<Element.ElementListener>(4);
	}
	
	public static abstract class ElementListener implements java.util.EventListener {
		public abstract void destroyed(EventObject e);
	}
	
	public void addElementListener(ElementListener listener) {
		listeners.add(listener);
	}
	
	private void destroyed() {
		Iterator<ElementListener> iter = listeners.iterator();
		while(iter.hasNext()) {
			iter.next().destroyed(new EventObject(this));
		}
	}
}